package dataflowanalysis;

import soot.Local;
import soot.Unit;
import soot.Value;
import soot.ValueBox;
import soot.jimple.BinopExpr;
import soot.toolkits.graph.DirectedGraph;
import soot.toolkits.scalar.ArraySparseSet;
import soot.toolkits.scalar.BackwardFlowAnalysis;
import soot.toolkits.scalar.FlowSet;

public class VeryBusyExpressions extends BackwardFlowAnalysis<Unit, FlowSet<Value>> {
    public VeryBusyExpressions(DirectedGraph<Unit> g) {
        super(g);
        doAnalysis();

    }

    @Override
    protected void flowThrough(FlowSet<Value> in, Unit d, FlowSet<Value> out) {
        kill(in, d, out);
        gen(out, d);
    }

    /**
     * The kill set is generated by iterating over the def-boxes
     * of the unit. For each local defined in the unit we iterate
     * over the binopExps in the inSet, and check whether they use
     * that local. If so, it is added to the kill set.
     *
     * @param inSet  the set flowing into the unit
     * @param u      the unit being flown through
     * @param outSet the set flowing out of the unit
     */
    private void kill(FlowSet<Value> inSet, Unit u, FlowSet<Value> outSet) {
        // out <- (in - expr containing locals defined in d) union out
        FlowSet<Value> kills = new ArraySparseSet<>();
        for (ValueBox defBox : u.getDefBoxes()) {
            if (!(defBox.getValue() instanceof Local)) {
                continue;
            }

            for (Value e : inSet) {
                System.out.println("in value " + e.toString());
                for (ValueBox useBox : e.getUseBoxes()) {
                    System.out.println("use value " + useBox.getValue().toString());
                    if (useBox.getValue() instanceof Local &&
                            useBox.getValue().equivTo(defBox.getValue()))
                        kills.add(e);
                }
            }
        }
        inSet.difference(kills, outSet);

    }

    /**
     * Performs gens by iterating over the units use-boxes.
     * If the value of a use-box is a binopExp then we add
     * it to the outSet.
     *
     * @param outSet the set flowing out of the unit
     * @param u      the unit being flown through
     */
    private void gen(FlowSet<Value> outSet, Unit u) {
        for (ValueBox useBox : u.getUseBoxes()) {
            if (useBox.getValue() instanceof BinopExpr)
                outSet.add(useBox.getValue());
        }
    }

    @Override
    protected FlowSet<Value> newInitialFlow() {
        return new ArraySparseSet<>();
    }

    @Override
    protected FlowSet<Value> entryInitialFlow() {
        return new ArraySparseSet<>();
    }

    @Override
    protected void merge(FlowSet<Value> in1, FlowSet<Value> in2, FlowSet<Value> out) {
        in1.intersection(in2, out);
    }

    @Override
    protected void copy(FlowSet source, FlowSet dest) {
        source.copy(dest);
    }
}
